#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>

#include "imbus_internal.h"
#include "im_string.h"
#include "im_key_codes.h"
#include "im_key_mappings.h"

typedef struct
{
    IMUInt32      mask;
    const IMChar *name;
} IMKeyModifierNamePair;

/* Sorted by name */
static IMKeyModifierNamePair im_key_modifier_names [] =
{
    { IM_KEY_ALT_MASK,          "Alt"       },
    { IM_KEY_CAPS_LOCK_MASK,    "CapsLock"  },
    { IM_KEY_CTRL_MASK,         "Ctrl"      },
    { IM_KEY_HYPER_MASK,        "Hyper"     },
    { IM_KEY_META_MASK,         "Meta"      },
    { IM_KEY_NUM_LOCK_MASK,     "NumLock"   },
    { IM_KEY_RELEASE_MASK,      "Release"   },
    { IM_KEY_SHIFT_MASK,        "Shift"     },
    { IM_KEY_SUPER_MASK,        "Super"     },
};

static int
compare_key_name_code_pair_by_code (IMConstPointer v1, IMConstPointer v2)
{
    return ((int)((const IMKeyNameCodePair*)v1)->key_code) -
           ((int)((const IMKeyNameCodePair*)v2)->key_code);
}

IMString*
im_key_get_name (IMUInt32 keyval)
{
    IMSize i;
    IMKeyNameCodePair *p;
    IMKeyNameCodePair  tmp;
    IMString *name = im_string_new (0);

    _im_assert (name);

    for (i = 0; i < IM_N_ELEMENTS(im_key_modifier_names); ++i) {
        if (im_key_check_mask(keyval,im_key_modifier_names[i].mask)) {
            im_string_append_str (name,im_key_modifier_names[i].name, -1);
            im_string_append_char (name, '+');
        }
    }

    tmp.name = 0;
    tmp.key_code = im_key_get_code (keyval);
    p = im_lower_bound (im_key_code_to_name_map,
                        IM_N_ELEMENTS(im_key_code_to_name_map),
                        sizeof (IMKeyNameCodePair),
                        &tmp,
                        compare_key_name_code_pair_by_code);

    if (p && p < im_key_code_to_name_map + IM_N_ELEMENTS(im_key_code_to_name_map) &&
        p->key_code == tmp.key_code)
        im_string_append_str (name,p->name,-1);
    else
        im_string_append_printf (name, "0x%04x", tmp.key_code);

    return name;
}

static int
compare_key_modifier_name_by_name (IMConstPointer v1, IMConstPointer v2, IMPointer u)
{
    return strncmp (((const IMKeyModifierNamePair*)v1)->name,
                    ((const IMKeyModifierNamePair*)v2)->name,
                    IM_POINTER_TO_UINT(u));
}

static int
compare_key_name_code_pair_by_name (IMConstPointer v1, IMConstPointer v2)
{
    return strcmp (((const IMKeyNameCodePair*)v1)->name, ((const IMKeyNameCodePair*)v2)->name);
}

IMUInt32
im_key_from_name (const IMString *name)
{
    IMUInt32 keyval = 0;
    IMSize prev,cur;
    const IMChar *cstr;

    _im_return_val_if_fail (IM_IS_STRING(name), 0);

    prev = 0;
    cur = im_string_find_char (name, '+', 0);
    cstr = im_string_c_str (name);

    while (cur != IM_STRING_NPOS) {
        IMKeyModifierNamePair mnp;
        IMKeyModifierNamePair *p;
        mnp.mask = 0;
        mnp.name = cstr + prev;

        p = im_lower_bound_with_data (im_key_modifier_names,
                                      IM_N_ELEMENTS(im_key_modifier_names),
                                      sizeof (IMKeyModifierNamePair),
                                      &mnp,
                                      compare_key_modifier_name_by_name,
                                      IM_UINT_TO_POINTER(cur - prev));

        if (p && p < im_key_modifier_names + IM_N_ELEMENTS(im_key_modifier_names) &&
            strlen (p->name) == cur - prev && strncmp (mnp.name, p->name, cur - prev) == 0)
            im_key_set_mask (keyval, p->mask);

        prev = cur + 1;
        cur = im_string_find_char (name, '+', prev + 1);
    }

    if (prev < im_string_length (name)) {
        if (prev + 3 < im_string_length (name) && cstr [prev] == '0' &&
            (cstr [prev+1] == 'x' || cstr [prev+1] == 'X')) {
            im_key_set_code (keyval, strtol(cstr + prev + 2, 0, 16));
        } else {
            IMKeyNameCodePair  ncp;
            IMKeyNameCodePair *p;
            ncp.key_code = 0;
            ncp.name = cstr + prev;
            p = im_lower_bound (im_key_name_to_code_map,
                                IM_N_ELEMENTS(im_key_name_to_code_map),
                                sizeof (IMKeyNameCodePair),
                                &ncp,
                                compare_key_name_code_pair_by_name);
            if (p && p < im_key_name_to_code_map + IM_N_ELEMENTS(im_key_name_to_code_map) &&
                strcmp (p->name, ncp.name) == 0)
                im_key_set_code (keyval, p->key_code);
        }
    }

    return keyval;
}

static int
compare_key_code_x11keysym_pair_by_code (IMConstPointer v1, IMConstPointer v2)
{
    return ((int)((const IMKeyCodeX11keysymPair*)v1)->key_code) -
           ((int)((const IMKeyCodeX11keysymPair*)v2)->key_code);
}

static int
compare_key_code_x11keysym_pair_by_sym (IMConstPointer v1, IMConstPointer v2)
{
    return ((int)((const IMKeyCodeX11keysymPair*)v1)->x11_keysym) -
           ((int)((const IMKeyCodeX11keysymPair*)v2)->x11_keysym);
}

IMUInt32
im_key_code_to_x11keysym (IMUInt32 keycode)
{
    IMKeyCodeX11keysymPair *p;
    IMKeyCodeX11keysymPair  tmp;

    /* Special cases */
    if (keycode >= IM_KEY_SPECIAL_START && keycode <= IM_KEY_SPECIAL_END)
        return keycode - IM_KEY_SPECIAL_START;

    tmp.key_code = im_key_get_code (keycode);
    tmp.x11_keysym = 0;

    p = im_lower_bound (im_key_code_to_x11keysym_map,
                        IM_N_ELEMENTS(im_key_code_to_x11keysym_map),
                        sizeof (IMKeyCodeX11keysymPair),
                        &tmp,
                        compare_key_code_x11keysym_pair_by_code);

    if (p && p < im_key_code_to_x11keysym_map + IM_N_ELEMENTS(im_key_code_to_x11keysym_map) &&
        p->key_code == tmp.key_code)
        return p->x11_keysym;

    return 0;
}

IMUInt32
im_key_code_from_x11keysym (IMUInt32 x11keysym)
{
    IMKeyCodeX11keysymPair *p;
    IMKeyCodeX11keysymPair  tmp;

    tmp.key_code = 0;
    tmp.x11_keysym = x11keysym;

    p = im_lower_bound (im_x11keysym_to_key_code_map,
                        IM_N_ELEMENTS(im_x11keysym_to_key_code_map),
                        sizeof (IMKeyCodeX11keysymPair),
                        &tmp,
                        compare_key_code_x11keysym_pair_by_sym);

    if (p && p < im_x11keysym_to_key_code_map + IM_N_ELEMENTS(im_x11keysym_to_key_code_map) &&
        p->x11_keysym == tmp.x11_keysym)
        return p->key_code;

    return 0;
}

/*
vi:ts=4:nowrap:ai:expandtab
*/
